home *** CD-ROM | disk | FTP | other *** search
Wrap
Text File | 2002-06-30 | 45.5 KB | 2,042 lines
// Copyright (C) 2001-2002 Raven Software. // #include "g_local.h" #include "../../ui/menudef.h" int AcceptBotCommand(char *cmd, gentity_t *pl); /* ================== DeathmatchScoreboardMessage ================== */ void DeathmatchScoreboardMessage( gentity_t *ent ) { char entry[1024]; char string[1400]; int stringlength; int i, j; gclient_t *cl; int numSorted; // send the latest information on all clients string[0] = 0; stringlength = 0; numSorted = level.numConnectedClients; for (i=0 ; i < numSorted ; i++) { int ping; cl = &level.clients[level.sortedClients[i]]; if ( cl->pers.connected == CON_CONNECTING ) { ping = -1; } else { ping = cl->ps.ping < 999 ? cl->ps.ping : 999; } Com_sprintf (entry, sizeof(entry), " %i %i %i %i %i %i %i %i %i", level.sortedClients[i], cl->sess.score, cl->sess.kills, cl->sess.deaths, ping, (level.time - cl->pers.enterTime)/60000, (cl->sess.ghost || cl->ps.pm_type == PM_DEAD) ? qtrue : qfalse, g_entities[level.sortedClients[i]].s.gametypeitems, g_teamkillDamageMax.integer ? 100 * cl->sess.teamkillDamage / g_teamkillDamageMax.integer : 0 ); j = strlen(entry); if (stringlength + j > 1022 ) { break; } strcpy (string + stringlength, entry); stringlength += j; } trap_SendServerCommand( ent-g_entities, va("scores %i %i %i%s", i, level.teamScores[TEAM_RED], level.teamScores[TEAM_BLUE], string ) ); } /* ================== Cmd_Score_f Request current scoreboard information ================== */ void Cmd_Score_f( gentity_t *ent ) { DeathmatchScoreboardMessage( ent ); } /* ================== CheatsOk ================== */ qboolean CheatsOk( gentity_t *ent ) { if ( !g_cheats.integer ) { trap_SendServerCommand( ent-g_entities, va("print \"Cheats are not enabled on this server.\n\"")); return qfalse; } if ( ent->health <= 0 ) { trap_SendServerCommand( ent-g_entities, va("print \"You must be alive to use this command.\n\"")); return qfalse; } return qtrue; } /* ================== ConcatArgs ================== */ char *ConcatArgs( int start ) { int i, c, tlen; static char line[MAX_STRING_CHARS]; int len; char arg[MAX_STRING_CHARS]; len = 0; c = trap_Argc(); for ( i = start ; i < c ; i++ ) { trap_Argv( i, arg, sizeof( arg ) ); tlen = strlen( arg ); if ( len + tlen >= MAX_STRING_CHARS - 1 ) { break; } memcpy( line + len, arg, tlen ); len += tlen; if ( i != c - 1 ) { line[len] = ' '; len++; } } line[len] = 0; return line; } /* ================== SanitizeString Remove case and control characters ================== */ void SanitizeString( char *in, char *out ) { while ( *in ) { if ( *in == 27 ) { in += 2; // skip color code continue; } if ( *in < 32 ) { in++; continue; } *out++ = tolower( *in++ ); } *out = 0; } /* ================== G_ClientNumberFromName Finds the client number of the client with the given name ================== */ int G_ClientNumberFromName ( const char* name ) { char s2[MAX_STRING_CHARS]; char n2[MAX_STRING_CHARS]; int i; gclient_t* cl; // check for a name match SanitizeString( (char*)name, s2 ); for ( i=0, cl=level.clients ; i < level.numConnectedClients ; i++, cl++ ) { SanitizeString( cl->pers.netname, n2 ); if ( !strcmp( n2, s2 ) ) { return i; } } return -1; } /* ================== ClientNumberFromString Returns a player number for either a number or name string Returns -1 if invalid ================== */ int ClientNumberFromString( gentity_t *to, char *s ) { gclient_t *cl; int idnum; char s2[MAX_STRING_CHARS]; char n2[MAX_STRING_CHARS]; // numeric values are just slot numbers if (s[0] >= '0' && s[0] <= '9') { idnum = atoi( s ); if ( idnum < 0 || idnum >= level.maxclients ) { trap_SendServerCommand( to-g_entities, va("print \"Bad client slot: %i\n\"", idnum)); return -1; } cl = &level.clients[idnum]; if ( cl->pers.connected != CON_CONNECTED ) { trap_SendServerCommand( to-g_entities, va("print \"Client %i is not active\n\"", idnum)); return -1; } return idnum; } // check for a name match SanitizeString( s, s2 ); for ( idnum=0,cl=level.clients ; idnum < level.maxclients ; idnum++,cl++ ) { if ( cl->pers.connected != CON_CONNECTED ) { continue; } SanitizeString( cl->pers.netname, n2 ); if ( !strcmp( n2, s2 ) ) { return idnum; } } trap_SendServerCommand( to-g_entities, va("print \"User %s is not on the server\n\"", s)); return -1; } /* ================== Cmd_Drop_f Drops the currenty selected weapon ================== */ void Cmd_Drop_f ( gentity_t* ent ) { gentity_t* dropped; // spectators cant drop anything since they dont have anything if ( ent->client->sess.team == TEAM_SPECTATOR ) { return; } // Ghosts and followers cant drop stuff if ( ent->client->ps.pm_flags & (PMF_GHOST|PMF_FOLLOW) ) { return; } // Drop the weapon the client wanted to drop dropped = G_DropWeapon ( ent, atoi(ConcatArgs( 1 )), 3000 ); if ( !dropped ) { return; } } /* ================== Cmd_DropItem_f Drops the gametype items the player is carrying ================== */ void Cmd_DropItem_f ( gentity_t* ent ) { // spectators cant drop anything since they dont have anything if ( ent->client->sess.team == TEAM_SPECTATOR ) { return; } // Ghosts and followers cant drop stuff if ( ent->client->ps.pm_flags & (PMF_GHOST|PMF_FOLLOW) ) { return; } // Nothing to drop if ( !ent->client->ps.stats[STAT_GAMETYPE_ITEMS] ) { return; } G_DropGametypeItems ( ent ); } /* ================== Cmd_Give_f Give items to a client ================== */ void Cmd_Give_f (gentity_t *ent) { char *name; gitem_t *it; int i; qboolean give_all; gentity_t *it_ent; trace_t trace; char arg[MAX_QPATH]; int start; int end; int l; trap_Argv( 1, arg, sizeof( arg ) ); if ( !Q_stricmp ( arg, "me" ) ) { start = ent->s.number; end = start + 1; } else if ( !Q_stricmp ( arg, "all" ) ) { start = 0; end = MAX_CLIENTS; } else { start = atoi ( arg ); end = start + 1; } for ( l = start; l < end; l ++ ) { ent = &g_entities[l]; if ( !ent->inuse ) { continue; } if ( G_IsClientDead ( ent->client ) ) { continue; } if ( !CheatsOk( ent ) ) { return; } name = ConcatArgs( 2 ); if (Q_stricmp(name, "all") == 0) give_all = qtrue; else give_all = qfalse; if (give_all || Q_stricmp( name, "health") == 0) { ent->health = MAX_HEALTH; if (!give_all) continue; } if (give_all || Q_stricmp(name, "weapons") == 0) { ent->client->ps.stats[STAT_WEAPONS] = (1 << WP_NUM_WEAPONS) - 1 - ( 1 << WP_NONE ); if (!give_all) continue; } if (give_all || Q_stricmp(name, "ammo") == 0) { for ( i = WP_NONE + 1 ; i < WP_NUM_WEAPONS ; i++ ) { attackType_t a; for ( a = ATTACK_NORMAL; a < ATTACK_MAX; a ++ ) { ent->client->ps.clip[a][i] = weaponData[i].attack[a].clipSize; ent->client->ps.ammo[weaponData[i].attack[a].ammoIndex] = ammoData[weaponData[i].attack[a].ammoIndex].max; } } if (!give_all) continue; } if (give_all || Q_stricmp(name, "armor") == 0) { ent->client->ps.stats[STAT_ARMOR] = MAX_ARMOR; if (!give_all) continue; } // spawn a specific item right on the player if ( !give_all ) { it = BG_FindItem (name); if (!it) { continue; } if ( it->giType == IT_GAMETYPE ) { continue; } it_ent = G_Spawn(); VectorCopy( ent->r.currentOrigin, it_ent->s.origin ); it_ent->classname = it->classname; G_SpawnItem (it_ent, it); FinishSpawningItem(it_ent ); memset( &trace, 0, sizeof( trace ) ); Touch_Item (it_ent, ent, &trace); if (it_ent->inuse) { G_FreeEntity( it_ent ); } } } } /* ================== Cmd_God_f Sets client to godmode argv(0) god ================== */ void Cmd_God_f (gentity_t *ent) { char *msg; if ( !CheatsOk( ent ) ) { return; } ent->flags ^= FL_GODMODE; if (!(ent->flags & FL_GODMODE) ) msg = "godmode OFF\n"; else msg = "godmode ON\n"; trap_SendServerCommand( ent-g_entities, va("print \"%s\"", msg)); } /* ================== Cmd_Notarget_f Sets client to notarget argv(0) notarget ================== */ void Cmd_Notarget_f( gentity_t *ent ) { char *msg; if ( !CheatsOk( ent ) ) { return; } ent->flags ^= FL_NOTARGET; if (!(ent->flags & FL_NOTARGET) ) msg = "notarget OFF\n"; else msg = "notarget ON\n"; trap_SendServerCommand( ent-g_entities, va("print \"%s\"", msg)); } /* ================== Cmd_Noclip_f argv(0) noclip ================== */ void Cmd_Noclip_f( gentity_t *ent ) { char *msg; if ( !CheatsOk( ent ) ) { return; } if ( ent->client->noclip ) { msg = "noclip OFF\n"; } else { msg = "noclip ON\n"; } ent->client->noclip = !ent->client->noclip; trap_SendServerCommand( ent-g_entities, va("print \"%s\"", msg)); } /* ================== Cmd_LevelShot_f This is just to help generate the level pictures for the menus. It goes to the intermission immediately and sends over a command to the client to resize the view, hide the scoreboard, and take a special screenshot ================== */ void Cmd_LevelShot_f( gentity_t *ent ) { if ( !CheatsOk( ent ) ) { return; } BeginIntermission(); trap_SendServerCommand( ent-g_entities, "clientLevelShot" ); } /* ================= Cmd_Kill_f ================= */ void Cmd_Kill_f( gentity_t *ent ) { // No killing yourself if your a spectator if ( G_IsClientSpectating ( ent->client ) ) { return; } // No killing yourself if your dead if ( G_IsClientDead ( ent->client ) ) { return; } ent->flags &= ~FL_GODMODE; ent->client->ps.stats[STAT_HEALTH] = ent->health = -999; player_die (ent, ent, ent, 100000, MOD_SUICIDE, HL_NONE, vec3_origin ); } /* ================= BroadCastTeamChange Let everyone know about a team change ================= */ void BroadcastTeamChange( gclient_t *client, int oldTeam ) { switch ( client->sess.team ) { case TEAM_RED: trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the red team.\n\"", client->pers.netname) ); break; case TEAM_BLUE: trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the blue team.\n\"", client->pers.netname)); break; case TEAM_SPECTATOR: if ( oldTeam != TEAM_SPECTATOR ) { trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the spectators.\n\"", client->pers.netname)); } break; case TEAM_FREE: trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the battle.\n\"", client->pers.netname)); break; } } /* ================= SetTeam ================= */ void SetTeam( gentity_t *ent, char *s, const char* identity ) { int team; int oldTeam; gclient_t *client; int clientNum; spectatorState_t specState; int specClient; qboolean ghost; qboolean noOutfittingChange = qfalse; // see what change is requested // client = ent->client; clientNum = client - level.clients; specClient = 0; specState = SPECTATOR_NOT; // If an identity was specified then inject it into // the clients userinfo if ( identity ) { char userinfo[MAX_INFO_STRING]; trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); if ( Q_stricmp ( identity, Info_ValueForKey ( userinfo, "identity" ) ) ) { Info_SetValueForKey ( userinfo, "identity", identity ); Info_SetValueForKey ( userinfo, "team_identity", identity ); trap_SetUserinfo ( clientNum, userinfo ); } else { identity = NULL; } } if ( !Q_stricmp( s, "follow1" ) ) { team = TEAM_SPECTATOR; specState = SPECTATOR_FOLLOW; specClient = -1; } else if ( !Q_stricmp( s, "follow2" ) ) { team = TEAM_SPECTATOR; specState = SPECTATOR_FOLLOW; specClient = -2; } else if ( !Q_stricmp( s, "spectator" ) || !Q_stricmp( s, "s" ) ) { team = TEAM_SPECTATOR; specState = SPECTATOR_FREE; } else if ( level.gametypeData->teams ) { // if running a team game, assign player to one of the teams specState = SPECTATOR_NOT; if ( !Q_stricmp( s, "red" ) || !Q_stricmp( s, "r" ) ) { team = TEAM_RED; } else if ( !Q_stricmp( s, "blue" ) || !Q_stricmp( s, "b" ) ) { team = TEAM_BLUE; } else { // pick the team with the least number of players team = PickTeam( clientNum ); } if ( g_teamForceBalance.integer ) { int counts[TEAM_NUM_TEAMS]; counts[TEAM_BLUE] = TeamCount( ent->client->ps.clientNum, TEAM_BLUE, NULL ); counts[TEAM_RED] = TeamCount( ent->client->ps.clientNum, TEAM_RED, NULL ); // We allow a spread of two if ( team == TEAM_RED && counts[TEAM_RED] - counts[TEAM_BLUE] > 1 ) { trap_SendServerCommand( ent->client->ps.clientNum, "cp \"Red team has too many players.\n\"" ); // ignore the request return; } if ( team == TEAM_BLUE && counts[TEAM_BLUE] - counts[TEAM_RED] > 1 ) { trap_SendServerCommand( ent->client->ps.clientNum, "cp \"Blue team has too many players.\n\"" ); // ignore the request return; } // It's ok, the team we are switching to has less or same number of players } } else { // force them to spectators if there aren't any spots free team = TEAM_FREE; } // override decision if limiting the players if ( g_maxGameClients.integer > 0 && level.numNonSpectatorClients >= g_maxGameClients.integer ) { team = TEAM_SPECTATOR; } // decide if we will allow the change oldTeam = client->sess.team; ghost = client->sess.ghost; if ( team == oldTeam && team != TEAM_SPECTATOR ) { if ( identity ) { // get and distribute relevent paramters client->pers.identity = NULL; ClientUserinfoChanged( clientNum ); } return; } noOutfittingChange = ent->client->noOutfittingChange; // he starts at 'base' client->pers.teamState.state = TEAM_BEGIN; if ( oldTeam != TEAM_SPECTATOR ) { if ( ghost ) { G_StopGhosting ( ent ); } else if ( !G_IsClientDead ( client ) ) { // Kill him (makes sure he loses flags, etc) ent->flags &= ~FL_GODMODE; ent->client->ps.stats[STAT_HEALTH] = ent->health = 0; player_die (ent, ent, ent, 100000, MOD_TEAMCHANGE, HL_NONE, vec3_origin ); ent->client->sess.ghost = qfalse; } } // If respawn interval start as a ghost if ( level.gametypeRespawnTime[ team ] ) { ghost = qtrue; } // they go to the end of the line if ( team == TEAM_SPECTATOR ) { client->sess.spectatorTime = level.time; } client->sess.team = team; client->sess.spectatorState = specState; client->sess.spectatorClient = specClient; // Always spawn into a ctf game using a respawn timer. if ( team != TEAM_SPECTATOR && level.gametypeData->respawnType == RT_INTERVAL ) { G_SetRespawnTimer ( ent ); ghost = qtrue; } BroadcastTeamChange( client, oldTeam ); // See if we should spawn as a ghost if ( team != TEAM_SPECTATOR && level.gametypeData->respawnType == RT_NONE ) { // If there are ghosts already then spawn as a ghost because // the game is already in progress. if ( (level.gametypeJoinTime && (level.time - level.gametypeJoinTime) > (g_roundjointime.integer * 1000)) || noOutfittingChange || client->sess.noTeamChange ) { ghost = qtrue; } // Spectator to a team doesnt count if ( oldTeam != TEAM_SPECTATOR ) { client->sess.noTeamChange = qtrue; } } // If a ghost, enforce it if ( ghost ) { // Make them a ghost again if ( team != TEAM_SPECTATOR ) { G_StartGhosting ( ent ); // get and distribute relevent paramters client->pers.identity = NULL; ClientUserinfoChanged( clientNum ); CalculateRanks(); return; } } // get and distribute relevent paramters client->pers.identity = NULL; ClientUserinfoChanged( clientNum ); CalculateRanks(); // Begin the clients new life on the their new team ClientBegin( clientNum ); } /* ================= G_StartGhosting Starts a client ghosting. This essentially will kill a player which is alive ================= */ void G_StartGhosting ( gentity_t* ent ) { int i; // Dont start ghosting if already ghosting if ( ent->client->sess.ghost ) { return; } ent->client->sess.ghost = qtrue; ent->client->sess.spectatorState = SPECTATOR_FREE; ent->client->sess.spectatorClient = -1; ent->client->ps.pm_flags |= PMF_GHOST; ent->client->ps.stats[STAT_HEALTH] = 100; ent->client->ps.pm_type = PM_SPECTATOR; ent->client->ps.pm_flags &= ~PMF_FOLLOW; trap_UnlinkEntity (ent); // stop any following clients for ( i = 0 ; i < level.maxclients ; i++ ) { if ( G_IsClientSpectating ( &level.clients[i] ) && level.clients[i].sess.spectatorState == SPECTATOR_FOLLOW && level.clients[i].sess.spectatorClient == ent->s.number ) { G_StopFollowing( &g_entities[i] ); } } } /* ================= G_StopGhosting Stops a client from ghosting. The client will be dead after this call ================= */ void G_StopGhosting ( gentity_t* ent ) { // Dont stop someone who isnt ghosting in the first place if ( !ent->client->sess.ghost ) { return; } ent->client->sess.ghost = qfalse; ent->client->ps.pm_flags &= ~PMF_GHOST; ent->client->ps.pm_flags &= ~PMF_FOLLOW; if ( ent->client->sess.team == TEAM_SPECTATOR ) { ent->client->ps.pm_type = PM_SPECTATOR; } else { ent->client->ps.pm_type = PM_DEAD; ent->health = ent->client->ps.stats[STAT_HEALTH] = 0; } } /* ================= G_StopFollowing If the client being followed leaves the game, or you just want to drop to free floating spectator mode ================= */ void G_StopFollowing( gentity_t *ent ) { // Cant stop following if not following in the first place if ( !(ent->client->ps.pm_flags&PMF_FOLLOW) ) { return; } // Clear the following variables ent->client->ps.pm_flags &= ~PMF_FOLLOW; ent->client->sess.spectatorState = SPECTATOR_FREE; ent->client->ps.clientNum = ent - g_entities; ent->client->ps.zoomFov = 0; ent->client->ps.loopSound = 0; ent->client->ps.pm_flags &= ~(PMF_GOGGLES_ON|PMF_ZOOM_FLAGS); ent->client->ps.persistant[PERS_TEAM] = ent->client->sess.team; ent->r.svFlags &= ~SVF_BOT; // Ghots dont really become spectators, just psuedo spectators if ( ent->client->sess.ghost ) { // Do a start and stop to ensure the variables are all set properly G_StopGhosting ( ent ); G_StartGhosting ( ent ); } else { ent->client->sess.team = TEAM_SPECTATOR; ent->client->ps.persistant[ PERS_TEAM ] = TEAM_SPECTATOR; } // If we were in fact following someone, then make the angles and origin nice for // when we stop if ( ent->client->sess.spectatorClient != -1 ) { gclient_t* cl = &level.clients[ent->client->sess.spectatorClient]; int i; for ( i = 0; i < 3; i ++ ) { ent->client->ps.delta_angles[i] = ANGLE2SHORT(cl->ps.viewangles[i] - SHORT2ANGLE(ent->client->pers.cmd.angles[i])); } VectorCopy ( cl->ps.viewangles, ent->client->ps.viewangles ); VectorCopy ( cl->ps.origin, ent->client->ps.origin ); VectorClear ( ent->client->ps.velocity ); ent->client->ps.movementDir = 0; BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); } ent->client->sess.spectatorClient = -1; } /* ================= Cmd_Team_f ================= */ void Cmd_Team_f( gentity_t *ent ) { char team[MAX_TOKEN_CHARS]; char identity[MAX_TOKEN_CHARS]; // Need at least the team specified in the arguments if ( trap_Argc() < 2 ) { int oldTeam = ent->client->sess.team; switch ( oldTeam ) { case TEAM_BLUE: trap_SendServerCommand( ent-g_entities, "print \"Blue team\n\"" ); break; case TEAM_RED: trap_SendServerCommand( ent-g_entities, "print \"Red team\n\"" ); break; case TEAM_FREE: trap_SendServerCommand( ent-g_entities, "print \"Free team\n\"" ); break; case TEAM_SPECTATOR: trap_SendServerCommand( ent-g_entities, "print \"Spectator team\n\"" ); break; } return; } // Limit how often one can switch team if ( ent->client->switchTeamTime > level.time ) { trap_SendServerCommand( ent-g_entities, "print \"May not switch teams more than once per 5 seconds.\n\"" ); return; } trap_Argv( 1, team, sizeof( team ) ); trap_Argv( 2, identity, sizeof( identity ) ); SetTeam( ent, team, identity[0]?identity:NULL ); // Remember the team switch time so they cant do it again really quick ent->client->switchTeamTime = level.time + 5000; } /* ================= Cmd_Follow_f ================= */ void Cmd_Follow_f( gentity_t *ent ) { int i; char arg[MAX_TOKEN_CHARS]; if ( trap_Argc() != 2 ) { if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { G_StopFollowing( ent ); } return; } trap_Argv( 1, arg, sizeof( arg ) ); i = ClientNumberFromString( ent, arg ); if ( i == -1 ) { return; } // can't follow self if ( &level.clients[ i ] == ent->client ) { return; } // cant cycle to dead people if ( level.clients[i].ps.pm_type == PM_DEAD ) { return; } // can't follow another spectator if ( G_IsClientSpectating ( &level.clients[ i ] ) ) { return; } // Dissallow following of the enemy if the cvar is set if ( level.gametypeData->teams && !g_followEnemy.integer && ent->client->sess.team != TEAM_SPECTATOR ) { // Are they on the same team? if ( level.clients[ i ].sess.team != ent->client->sess.team ) { return; } } // first set them to spectator as long as they arent a ghost if ( !ent->client->sess.ghost && ent->client->sess.team != TEAM_SPECTATOR ) { SetTeam( ent, "spectator", NULL ); } ent->client->sess.spectatorState = SPECTATOR_FOLLOW; ent->client->sess.spectatorClient = i; } /* ================= Cmd_FollowCycle_f ================= */ void Cmd_FollowCycle_f( gentity_t *ent, int dir ) { int clientnum; int deadclient; int original; // first set them to spectator if ( !ent->client->sess.ghost && ent->client->sess.team != TEAM_SPECTATOR ) { SetTeam( ent, "spectator", NULL ); } if ( dir != 1 && dir != -1 ) { Com_Error( ERR_FATAL, "Cmd_FollowCycle_f: bad dir %i", dir ); } if ( ent->client->sess.spectatorClient == -1 ) { clientnum = original = ent->s.number; } else { clientnum = original = ent->client->sess.spectatorClient; } deadclient = -1; do { clientnum += dir; if ( clientnum >= level.maxclients ) { clientnum = 0; } if ( clientnum < 0 ) { clientnum = level.maxclients - 1; } // can only follow connected clients if ( level.clients[ clientnum ].pers.connected != CON_CONNECTED ) { continue; } // can't follow another spectator if ( G_IsClientSpectating ( &level.clients[ clientnum ] ) ) { continue; } // Cant switch to dead people unless there is nobody else to switch to if ( G_IsClientDead ( &level.clients[clientnum] ) ) { deadclient = clientnum; continue; } // Dissallow following of the enemy if the cvar is set if ( level.gametypeData->teams && !g_followEnemy.integer && ent->client->sess.team != TEAM_SPECTATOR ) { // Are they on the same team? if ( level.clients[ clientnum ].sess.team != ent->client->sess.team ) { continue; } } // this is good, we can use it ent->client->sess.spectatorClient = clientnum; ent->client->sess.spectatorState = SPECTATOR_FOLLOW; return; } while ( clientnum != original ); // If being forced to follow and there is a dead client to jump to, then jump to them now if ( deadclient != -1 && g_forceFollow.integer ) { // this is good, we can use it ent->client->sess.spectatorClient = deadclient; ent->client->sess.spectatorState = SPECTATOR_FOLLOW; return; } G_StopFollowing( ent ); // leave it where it was } /* ================== G_SayTo ================== */ static void G_SayTo( gentity_t *ent, gentity_t *other, int mode, const char *name, const char *message ) { qboolean ghost = qfalse; qboolean spec = qfalse; const char* type; if (!other) { return; } if (!other->inuse) { return; } if (!other->client) { return; } if ( other->client->pers.connected != CON_CONNECTED ) { return; } if ( mode == SAY_TEAM && !OnSameTeam(ent, other) ) { return; } if ( !level.intermissiontime && !level.intermissionQueued ) { // Spectators cant talk to alive people if ( ent->client->sess.team == TEAM_SPECTATOR ) { spec = qtrue; } if ( level.gametypeData->respawnType == RT_NONE ) { // Dead people cant talk to alive people if ( !spec && G_IsClientDead ( ent->client ) ) { ghost = qtrue; } // If the client we are talking to is alive then a check // must be made to see if this talker is alowed to speak to this person if ( ent->s.number != other->s.number && !G_IsClientDead ( other->client ) && !G_IsClientSpectating( other->client) && (ghost || spec)) { return; } } } type = ""; if ( ghost ) { type = "*ghost* "; } else if ( spec ) { type = "*spec* "; } trap_SendServerCommand( other-g_entities, va("%s %d \"%s%s%s\"", mode == SAY_TEAM ? "tchat" : "chat", ent->s.number, type, name, message)); } /* ================== G_GetChatPrefix ================== */ void G_GetChatPrefix ( gentity_t* ent, gentity_t* target, int mode, char* name, int nameSize ) { const char* namecolor; char location[64]; qboolean locationOk = qtrue; // Spectators and ghosts dont show locations if ( ent->client->ps.pm_type == PM_DEAD || G_IsClientSpectating ( ent->client ) ) { locationOk = qfalse; } if ( !level.gametypeData->teams && mode == SAY_TEAM ) { mode = SAY_ALL; } if ( level.gametypeData->teams ) { switch ( ent->client->sess.team ) { case TEAM_BLUE: namecolor = S_COLOR_BLUE; break; case TEAM_RED: namecolor = S_COLOR_RED; break; default: namecolor = S_COLOR_WHITE; break; } } else { namecolor = S_COLOR_WHITE; } switch ( mode ) { default: case SAY_ALL: Com_sprintf (name, nameSize, "%s%s%s: ", namecolor, ent->client->pers.netname, S_COLOR_WHITE ); break; case SAY_TEAM: if ( locationOk && Team_GetLocationMsg(ent, location, sizeof(location))) { Com_sprintf ( name, nameSize, "%s(%s%s) %s(%s): ", namecolor, ent->client->pers.netname, namecolor, S_COLOR_WHITE, location ); } else { Com_sprintf ( name, nameSize, "%s(%s%s)%s: ", namecolor, ent->client->pers.netname, namecolor, S_COLOR_WHITE ); } break; case SAY_TELL: if ( locationOk && target && level.gametypeData->teams && target->client->sess.team == ent->client->sess.team && Team_GetLocationMsg(ent, location, sizeof(location)) ) { Com_sprintf ( name, nameSize, "%s[%s%s] %s(%s): ", namecolor, ent->client->pers.netname, namecolor, S_COLOR_WHITE, location ); } else { Com_sprintf ( name, nameSize, "%s[%s%s]%s: ", namecolor, ent->client->pers.netname, namecolor, S_COLOR_WHITE ); } break; } strcat ( name, S_COLOR_GREEN ); } /* ================== G_Say ================== */ void G_Say ( gentity_t *ent, gentity_t *target, int mode, const char *chatText ) { int j; gentity_t *other; char text[MAX_SAY_TEXT]; char name[256]; // Logging stuff switch ( mode ) { case SAY_ALL: G_LogPrintf( "say: %s: %s\n", ent->client->pers.netname, chatText ); break; case SAY_TEAM: G_LogPrintf( "sayteam: %s: %s\n", ent->client->pers.netname, chatText ); break; } // Generate the chat prefix G_GetChatPrefix ( ent, target, mode, name, sizeof(name) ); // Save off the chat text Q_strncpyz( text, chatText, sizeof(text) ); if ( target ) { G_SayTo( ent, target, mode, name, text ); return; } // echo the text to the console if ( g_dedicated.integer ) { Com_Printf( "%s%s\n", name, text); } // send it to all the apropriate clients for (j = 0; j < level.numConnectedClients; j++) { other = &g_entities[level.sortedClients[j]]; G_SayTo( ent, other, mode, name, text ); } } /* ================== Cmd_Say_f ================== */ static void Cmd_Say_f( gentity_t *ent, int mode, qboolean arg0 ) { char *p; if ( trap_Argc () < 2 && !arg0 ) { return; } if (arg0) { p = ConcatArgs( 0 ); } else { p = ConcatArgs( 1 ); } G_Say( ent, NULL, mode, p ); } /* ================== Cmd_Tell_f ================== */ static void Cmd_Tell_f( gentity_t *ent ) { int targetNum; gentity_t *target; char *p; char arg[MAX_TOKEN_CHARS]; if ( trap_Argc () < 2 ) { return; } trap_Argv( 1, arg, sizeof( arg ) ); targetNum = atoi( arg ); if ( targetNum < 0 || targetNum >= level.maxclients ) { return; } target = &g_entities[targetNum]; if ( !target || !target->inuse || !target->client ) { return; } p = ConcatArgs( 2 ); G_LogPrintf( "tell: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, p ); G_Say( ent, target, SAY_TELL, p ); // don't tell to the player self if it was already directed to this player // also don't send the chat back to a bot if ( ent != target && !(ent->r.svFlags & SVF_BOT)) { G_Say( ent, ent, SAY_TELL, p ); } } static void G_VoiceTo ( gentity_t *ent, gentity_t *other, int mode, const char* name, const char *id, qboolean voiceonly ) { // Only team say is supported right now for voice chatting if (mode != SAY_TEAM) { return; } if (!other || !other->inuse || !other->client) { return; } if ( !OnSameTeam(ent, other) ) { return; } trap_SendServerCommand( other-g_entities, va("%s %d %d \"%s\" \"%s\"", "vtchat", voiceonly, ent->s.number, name, id)); } /* ================== G_CanVoiceGlobal Can we globaly speak right now ================== */ qboolean G_CanVoiceGlobal ( void ) { if ( level.gametypeData->teams && level.time - level.globalVoiceTime > 5000 ) { return qtrue; } return qfalse; } /* ================== G_VoiceGlobal says something out loud that everyone in the radius can hear ================== */ void G_VoiceGlobal ( gentity_t* ent, const char* id, qboolean force ) { if ( !ent ) { return; } if ( !level.gametypeData->teams ) { return; } if ( !force && level.time - level.globalVoiceTime < 5000 ) { return; } level.globalVoiceTime = level.time; trap_SendServerCommand( -1, va("vglobal %d \"%s\"", ent->s.number, id)); } /* ================== G_Voice ================== */ void G_Voice( gentity_t *ent, gentity_t *target, int mode, const char *id, qboolean voiceonly ) { int j; gentity_t *other; char name[MAX_SAY_TEXT]; // Spectators and ghosts dont talk if ( ent->client->ps.pm_type == PM_DEAD || G_IsClientSpectating ( ent->client ) ) { return; } // Voice flooding protection on? if ( g_voiceFloodCount.integer ) { // If this client has been penalized for voice chatting to much then dont allow the voice chat if ( ent->client->voiceFloodPenalty ) { if ( ent->client->voiceFloodPenalty > level.time ) { return; } // No longer penalized ent->client->voiceFloodPenalty = 0; } // See if this client flooded with voice chats ent->client->voiceFloodCount++; if ( ent->client->voiceFloodCount >= g_voiceFloodCount.integer ) { ent->client->voiceFloodCount = 0; ent->client->voiceFloodTimer = 0; ent->client->voiceFloodPenalty = level.time + g_voiceFloodPenalty.integer * 1000; trap_SendServerCommand( ent-g_entities, va("print \"Voice chat flooded, you will be able use voice chats again in (%d) seconds\n\"", g_voiceFloodPenalty.integer ) ); return; } } G_GetChatPrefix ( ent, target, mode, name, sizeof(name) ); if ( target ) { G_VoiceTo( ent, target, mode, name, id, voiceonly ); return; } // send it to all the apropriate clients for (j = 0; j < level.maxclients; j++) { other = &g_entities[j]; G_VoiceTo( ent, other, mode, name, id, voiceonly ); } } /* ================== Cmd_Voice_f ================== */ static void Cmd_Voice_f( gentity_t *ent, int mode, qboolean arg0, qboolean voiceonly ) { char *p; if ( trap_Argc () < 2 && !arg0 ) { return; } if (arg0) { p = ConcatArgs( 0 ); } else { p = ConcatArgs( 1 ); } G_Voice( ent, NULL, mode, p, voiceonly ); } /* ================== Cmd_Where_f ================== */ void Cmd_Where_f( gentity_t *ent ) { trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", vtos( ent->s.origin ) ) ); } /* ============ G_VoteDisabled determins if the given vote is disabled ============ */ int G_VoteDisabled ( const char* callvote ) { return trap_Cvar_VariableIntegerValue( va("novote_%s", callvote) ); } /* ================== Cmd_CallVote_f ================== */ void Cmd_CallVote_f( gentity_t *ent ) { int i; char arg1[MAX_STRING_TOKENS]; char arg2[MAX_STRING_TOKENS]; if ( !g_allowVote.integer ) { trap_SendServerCommand( ent-g_entities, "print \"Voting not allowed here.\n\"" ); return; } if ( level.intermissiontime || level.intermissionQueued ) { trap_SendServerCommand( ent-g_entities, "print \"Voting not allowed during intermission.\n\"" ); return; } // No voting within the minute of a map change if ( level.time - level.startTime < 1000 * 60 ) { trap_SendServerCommand( ent-g_entities, "print \"Cannot vote within the first minute of a map change.\n\"" ); return; } if ( level.numConnectedClients > 1 && level.numVotingClients == 1 ) { trap_SendServerCommand( ent-g_entities, "print \"You need at least 2 clients to call a vote.\n\"" ); return; } if ( level.voteTime ) { trap_SendServerCommand( ent-g_entities, "print \"A vote is already in progress.\n\"" ); return; } if ( ent->client->pers.voteCount >= MAX_VOTE_COUNT ) { trap_SendServerCommand( ent-g_entities, "print \"You have called the maximum number of votes.\n\"" ); return; } if ( ent->client->sess.team == TEAM_SPECTATOR ) { trap_SendServerCommand( ent-g_entities, "print \"Not allowed to call a vote as spectator.\n\"" ); return; } if ( ent->client->voteDelayTime > level.time ) { trap_SendServerCommand( ent-g_entities, va("print \"You are not allowed to vote within %d minute of a failed vote.\n\"", g_failedVoteDelay.integer ) ); return; } // Save the voting client id level.voteClient = ent->s.number; // make sure it is a valid command to vote on trap_Argv( 1, arg1, sizeof( arg1 ) ); trap_Argv( 2, arg2, sizeof( arg2 ) ); if( strchr( arg1, ';' ) || strchr( arg2, ';' ) ) { trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" ); return; } if ( !Q_stricmp( arg1, "map_restart" ) ) { } else if ( !Q_stricmp( arg1, "mapcycle" ) ) { } else if ( !Q_stricmp( arg1, "map" ) ) { } else if ( !Q_stricmp( arg1, "rmgmap" ) ) { } else if ( !Q_stricmp( arg1, "g_gametype" ) ) { } else if ( !Q_stricmp( arg1, "kick" ) ) { } else if ( !Q_stricmp( arg1, "clientkick" ) ) { } else if ( !Q_stricmp( arg1, "g_doWarmup" ) ) { } else if ( !Q_stricmp( arg1, "g_friendlyfire" ) ) { } else if ( !Q_stricmp( arg1, "timelimit" ) ) { } else if ( !Q_stricmp( arg1, "timeextension" ) ) { } else if ( !Q_stricmp( arg1, "scorelimit" ) ) { } else { trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" ); trap_SendServerCommand( ent-g_entities, "print \"Vote commands are: map_restart, nextmap, map <mapname>, g_gametype <n>, kick <player>, clientkick <clientnum>, g_doWarmup, timelimit <time>, scorelimit <score>.\n\"" ); return; } // see if this particular vote is disabled if ( G_VoteDisabled ( arg1 ) ) { trap_SendServerCommand( ent-g_entities, va("print \"The '%s' vote has been disabled on this server.\n\"", arg1) ); return; } // if there is still a vote to be executed if ( level.voteExecuteTime ) { level.voteExecuteTime = 0; trap_SendConsoleCommand( EXEC_APPEND, va("%s\n", level.voteString ) ); } // special case for g_gametype, check for bad values if ( !Q_stricmp( arg1, "g_gametype" ) ) { // Verify the gametype i = BG_FindGametype ( arg2 ); if ( i < 0 ) { trap_SendServerCommand( ent-g_entities, "print \"Invalid gametype.\n\"" ); return; } Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %s", arg1, arg2 ); Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s %s", arg1, bg_gametypeData[i].name ); } else if ( !Q_stricmp( arg1, "map" ) ) { Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %s", arg1, arg2 ); Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s", level.voteString ); } else if ( !Q_stricmp( arg1, "rmgmap" ) ) { char arg3[MAX_STRING_TOKENS]; char arg4[MAX_STRING_TOKENS]; trap_Argv( 3, arg3, sizeof( arg3 ) ); trap_Argv( 4, arg4, sizeof( arg4 ) ); Com_sprintf( level.voteString, sizeof( level.voteString ), "rmgmap 1 %s 2 %s 3 %s 4 \"%s\" 0", arg2, arg3, arg4, ConcatArgs ( 5 ) ); Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s", level.voteString ); } else if ( !Q_stricmp( arg1, "mapcycle" ) ) { if (!*g_mapcycle.string || !Q_stricmp ( g_mapcycle.string, "none" ) ) { trap_SendServerCommand( ent-g_entities, "print \"there is no map cycle currently set up.\n\"" ); return; } Com_sprintf( level.voteString, sizeof( level.voteString ), "mapcycle"); Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "nextmap" ); } else if ( !Q_stricmp ( arg1, "clientkick" ) ) { int n = atoi ( arg2 ); if ( n < 0 || n >= MAX_CLIENTS ) { trap_SendServerCommand( ent-g_entities, va("print \"invalid client number %d.\n\"", n ) ); return; } if ( g_entities[n].client->pers.connected == CON_DISCONNECTED ) { trap_SendServerCommand( ent-g_entities, va("print \"there is no client with the client number %d.\n\"", n ) ); return; } if ( g_voteKickBanTime.integer ) { Com_sprintf ( level.voteString, sizeof(level.voteString ), "banclient %s %d voted off server", arg2, g_voteKickBanTime.integer ); } else { Com_sprintf ( level.voteString, sizeof(level.voteString ), "clientkick %s", arg2 ); } Com_sprintf ( level.voteDisplayString, sizeof(level.voteDisplayString), "kick %s", g_entities[n].client->pers.netname ); } else if ( !Q_stricmp ( arg1, "kick" ) ) { int clientid = G_ClientNumberFromName ( arg2 ); if ( clientid == -1 ) { trap_SendServerCommand( ent-g_entities, va("print \"there is no client named '%s' currently on the server.\n\"", arg2 ) ); return; } if ( g_voteKickBanTime.integer ) { Com_sprintf ( level.voteString, sizeof(level.voteString ), "banclient %d %d voted off server", clientid, g_voteKickBanTime.integer ); } else { Com_sprintf ( level.voteString, sizeof(level.voteString ), "clientkick %d", clientid ); } Com_sprintf ( level.voteDisplayString, sizeof(level.voteDisplayString), "kick %s", g_entities[clientid].client->pers.netname ); } else if ( !Q_stricmp ( arg1, "timeextension" ) ) { if ( !g_timelimit.integer ) { trap_SendServerCommand( ent-g_entities, va("print \"There is no timelimit to extend.\n\"") ); return; } if ( !g_timeextension.integer ) { trap_SendServerCommand( ent-g_entities, va("print \"This server does not allow time extensions.\n\"") ); return; } Com_sprintf ( level.voteString, sizeof(level.voteString ), "extendtime %d", g_timeextension.integer ); Com_sprintf ( level.voteDisplayString, sizeof(level.voteDisplayString), "extend timelimit by %d minutes", g_timeextension.integer ); } else { Com_sprintf( level.voteString, sizeof( level.voteString ), "%s \"%s\"", arg1, arg2 ); Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s", level.voteString ); } trap_SendServerCommand( -1, va("print \"%s called a vote.\n\"", ent->client->pers.netname ) ); // start the voting, the caller autoamtically votes yes level.voteTime = level.time; level.voteYes = 1; level.voteNo = 0; for ( i = 0 ; i < level.maxclients ; i++ ) { level.clients[i].ps.eFlags &= ~EF_VOTED; } ent->client->ps.eFlags |= EF_VOTED; trap_SetConfigstring( CS_VOTE_TIME, va("%i,%i", level.voteTime, g_voteDuration.integer*1000 ) ); trap_SetConfigstring( CS_VOTE_STRING, level.voteDisplayString ); trap_SetConfigstring( CS_VOTE_YES, va("%i", level.voteYes ) ); trap_SetConfigstring( CS_VOTE_NO, va("%i", level.voteNo ) ); trap_SetConfigstring( CS_VOTE_NEEDED, va("%i", level.numVotingClients / 2 ) ); } /* ================== Cmd_Vote_f ================== */ void Cmd_Vote_f( gentity_t *ent ) { char msg[64]; if ( !level.voteTime ) { trap_SendServerCommand( ent-g_entities, "print \"No vote in progress.\n\"" ); return; } if ( ent->client->ps.eFlags & EF_VOTED ) { trap_SendServerCommand( ent-g_entities, "print \"Vote already cast.\n\"" ); return; } if ( ent->client->sess.team == TEAM_SPECTATOR ) { trap_SendServerCommand( ent-g_entities, "print \"Not allowed to vote as spectator.\n\"" ); return; } trap_SendServerCommand( ent-g_entities, "print \"Vote cast.\n\"" ); ent->client->ps.eFlags |= EF_VOTED; trap_Argv( 1, msg, sizeof( msg ) ); if ( msg[0] == 'y' || msg[1] == 'Y' || msg[1] == '1' ) { level.voteYes++; trap_SetConfigstring( CS_VOTE_YES, va("%i", level.voteYes ) ); } else { level.voteNo++; trap_SetConfigstring( CS_VOTE_NO, va("%i", level.voteNo ) ); } // a majority will be determined in CheckVote, which will also account // for players entering or leaving } /* ================= Cmd_SetViewpos_f ================= */ void Cmd_SetViewpos_f( gentity_t *ent ) { vec3_t origin, angles; char buffer[MAX_TOKEN_CHARS]; int i; if ( !g_cheats.integer ) { trap_SendServerCommand( ent-g_entities, va("print \"Cheats are not enabled on this server.\n\"")); return; } if ( trap_Argc() != 5 ) { trap_SendServerCommand( ent-g_entities, va("print \"usage: setviewpos x y z yaw\n\"")); return; } VectorClear( angles ); for ( i = 0 ; i < 3 ; i++ ) { trap_Argv( i + 1, buffer, sizeof( buffer ) ); origin[i] = atof( buffer ); } trap_Argv( 4, buffer, sizeof( buffer ) ); angles[YAW] = atof( buffer ); TeleportPlayer( ent, origin, angles ); } /* ================= ClientCommand ================= */ void ClientCommand( int clientNum ) { gentity_t *ent; char cmd[MAX_TOKEN_CHARS]; ent = g_entities + clientNum; if ( !ent->client ) { return; // not fully in game yet } trap_Argv( 0, cmd, sizeof( cmd ) ); //rww - redirect bot commands if (strstr(cmd, "bot_") && AcceptBotCommand(cmd, ent)) { return; } //end rww if (Q_stricmp (cmd, "say") == 0) { Cmd_Say_f (ent, SAY_ALL, qfalse); return; } if (Q_stricmp (cmd, "say_team") == 0) { Cmd_Say_f (ent, SAY_TEAM, qfalse); return; } if (Q_stricmp (cmd, "tell") == 0) { Cmd_Tell_f ( ent ); return; } if (Q_stricmp (cmd, "vsay_team") == 0) { Cmd_Voice_f (ent, SAY_TEAM, qfalse, qfalse); return; } if (Q_stricmp (cmd, "score") == 0) { Cmd_Score_f (ent); return; } if (Q_stricmp (cmd, "team") == 0) { Cmd_Team_f (ent); return; } // ignore all other commands when at intermission if (level.intermissiontime) { // Cmd_Say_f (ent, qfalse, qtrue); return; } if ( Q_stricmp ( cmd, "drop" ) == 0 ) Cmd_Drop_f ( ent ); else if (Q_stricmp (cmd, "dropitem" ) == 0 ) Cmd_DropItem_f ( ent ); else if (Q_stricmp (cmd, "give") == 0) Cmd_Give_f (ent); else if (Q_stricmp (cmd, "god") == 0) Cmd_God_f (ent); else if (Q_stricmp (cmd, "notarget") == 0) Cmd_Notarget_f (ent); else if (Q_stricmp (cmd, "noclip") == 0) Cmd_Noclip_f (ent); else if (Q_stricmp (cmd, "kill") == 0) Cmd_Kill_f (ent); else if (Q_stricmp (cmd, "levelshot") == 0) Cmd_LevelShot_f (ent); else if (Q_stricmp (cmd, "follow") == 0) Cmd_Follow_f (ent); else if (Q_stricmp (cmd, "follownext") == 0) Cmd_FollowCycle_f (ent, 1); else if (Q_stricmp (cmd, "followprev") == 0) Cmd_FollowCycle_f (ent, -1); else if (Q_stricmp (cmd, "where") == 0) Cmd_Where_f (ent); else if (Q_stricmp (cmd, "callvote") == 0) Cmd_CallVote_f (ent); else if (Q_stricmp (cmd, "vote") == 0) Cmd_Vote_f (ent); else if (Q_stricmp (cmd, "setviewpos") == 0) Cmd_SetViewpos_f( ent ); #ifdef _SOF2_BOTS else if (Q_stricmp (cmd, "addbot") == 0) trap_SendServerCommand( clientNum, va("print \"ADDBOT command can only be used via RCON\n\"" ) ); #endif else trap_SendServerCommand( clientNum, va("print \"unknown cmd %s\n\"", cmd ) ); }